{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Part 4, Topic 3: ChipWhisperer Analyzer CPA Attack (MAIN)\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "NOTE: This lab references some (commercial) training material on [ChipWhisperer.io](https://www.ChipWhisperer.io). You can freely execute and use the lab per the open-source license (including using it in your own courses if you distribute similarly), but you must maintain notice about this source location. Consider joining our training course to enjoy the full experience.\n",
    "\n",
    "---"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**SUMMARY:** *Last time, we saw how correlation can be used to recover an AES key, as well as the effectiveness of such an attack. In this lab, we'll repeat the attack with ChipWhisperer Analyzer and gain some additional information about the attack*\n",
    "\n",
    "**LEARNING OUTCOMES:**\n",
    "\n",
    "* Use ChipWhisperer Analyzer to perform a CPA attack\n",
    "* Plot additional information about the attack"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prerequisites\n",
    "\n",
    "Hold up! Before you continue, check you've done the following tutorials:\n",
    "\n",
    "* ☑ CPA on Firmware Implementation of AES (you should understand how a CPA attack works).\n",
    "* ☑ SCA101 Intro (you should have an idea of how to get hardware-specific versions running)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Projects\n",
    "\n",
    "There's no need for any models or SBox implementaions, or anything like that this time. Instead, everything's contained in ChipWhisperer Analyzer. Another change from previous tutorials is that we're using ChipWhisperer projects instead of numpy arrays, since most of ChipWhisperer Analyzer only works with ChipWhisperer projects.\n",
    "\n",
    "As usual, see the associated notebook for details of the trace capture (or existing project) and copy below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# There is a file with the same name of this lab but (HARDWARE) in title for using CW-Nano/CW-Lite/CW-Pro\n",
    "# There is a file with the same name of this lab but (SIMULATED) in title for using recorded data\n",
    "raise NotImplementedError(\"Insert code from (HARDWARE) or (SIMULATED) Notebook Here\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Before we continue on with our CPA attack, let's take a quick look at the projects:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# we can access wave, textin, etc as a whole with proj.traces\n",
    "for trace in proj.traces:\n",
    "    print(trace.wave[0], trace.textin, trace.textout, trace.key)\n",
    "\n",
    "# can also access individually with proj.waves, proj.textins, etc.\n",
    "for wave in proj.waves:\n",
    "    print(wave[0])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ChipWhisperer Analyzer\n",
    "\n",
    "We can access Analyzer via `chipwhisperer.analyzer`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer.analyzer as cwa"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We also have to set our leakage model to be the SBox output. ChipWhisperer Analyzer includes a bunch of different leakage models which are useful in different situations. We'll look more at that in SCA201."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "leak_model = cwa.leakage_models.sbox_output"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The rest of the setup only takes 1 line:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "attack = cwa.cpa(proj, leak_model)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "If you want to see the attack settings, you can print the cpa object:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(attack)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Running the attack is also done in a single line:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "results = attack.run()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's see if we got the AES key:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can get the full information from the attack by calling `results.find_maximums()`, which returns:\n",
    "\n",
    "```Python\n",
    "find_maxiums() ->\n",
    "    [subkey0_data, subkey1_data, subkey2_data, ...]\n",
    "    \n",
    "subkey0_data ->\n",
    "    [guess0, guess1, guess2, ...]\n",
    "    \n",
    "guess0 ->\n",
    "    (key_guess, location_of_max, correlation)\n",
    "```\n",
    "\n",
    "For example, if you want to print the correlation of the third best guess of the 4th subkey, you would run:\n",
    "\n",
    "```python\n",
    "print(attack_results.find_maximums()[4][3][2])\n",
    "```\n",
    "\n",
    "Note the \"point location of the max\" is normally not calculated/tracked, and thus returns as a 0. Using the pandas library lets us print them nicely in a DataFrame. We have to transpose the frame to get our expected orientation:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import pandas as pd\n",
    "stat_data = results.find_maximums()\n",
    "df = pd.DataFrame(stat_data).transpose()\n",
    "print(df.head())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Even better, we can use the `.style` method to customize this further. This also lets us chain formatting functions. For example, we can remove the extra 0 and clean up the data. Since we know the correct key, we can even do things like printing the key in a different colour! \n",
    "\n",
    "You can do lots of formatting thanks to the pandas library! Check out https://pandas.pydata.org/pandas-docs/stable/style.html for more details."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "key = proj.keys[0]\n",
    "def format_stat(stat):\n",
    "    return str(\"{:02X}<br>{:.3f}\".format(stat[0], stat[2]))\n",
    "\n",
    "def color_corr_key(row):\n",
    "    global key\n",
    "    ret = [\"\"] * 16\n",
    "    for i,bnum in enumerate(row):\n",
    "        if bnum[0] == key[i]:\n",
    "            ret[i] = \"color: red\"\n",
    "        else:\n",
    "            ret[i] = \"\"\n",
    "    return ret\n",
    "\n",
    "df.head().style.format(format_stat).apply(color_corr_key, axis=1)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see red numbers printed at the top of a table. Congratulations, you've now completed a successful CPA attack against AES!\n",
    "\n",
    "Next, we'll look at how we can use some of Analyzer's other features to improve the attack process, as well as better interpret the data we have."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Reporting Intervals\n",
    "\n",
    "When we ran `attack.run()`, we processed all of the traces before getting any information back. ChipWhisperer Analyzer actually uses the \"online\" correlation calculation that we mentioned last time, meaning we can get feedback during the attack. This can be done by creating a callback function and passing it to `attack.run()`. This function is called each time we pass the update interval (default 25, which is the second parameter for `attack.run()`).\n",
    "\n",
    "Let's use this to update our table every 10 traces. Most of this is just putting our existing code into the callback function. We also need use the `clear_output` function to clear the table, as well as `display()` to actually get it to show up:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import clear_output\n",
    "import numpy as np\n",
    "        \n",
    "def stats_callback():\n",
    "    results = attack.results\n",
    "    results.set_known_key(key)\n",
    "    stat_data = results.find_maximums()\n",
    "    df = pd.DataFrame(stat_data).transpose()\n",
    "    clear_output(wait=True)\n",
    "    display(df.head().style.format(format_stat).apply(color_corr_key,axis=1))\n",
    "    \n",
    "results = attack.run(stats_callback, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "A default jupyter callback is also available - the following **three lines** are all you need to run an attack!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import chipwhisperer as cw\n",
    "cb = cwa.get_jupyter_callback(attack)\n",
    "results = attack.run(cb, 10)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Here we used a reporting interval of 10 traces. Depending on the attack and what you want to learn from it, you may want to use higher or lower values: in general reporting less often is faster, but more frequent reporting can allow you to end a long attack early. More frequent reporting also increases the resolution of some plot data (which we will look at next)."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot Data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Analyzer also includes a module to create plots to help you interpret the data. These act on one subkey at a time and return some data that we can plot using bokeh (or your graphing module of choice). Let's start by grabbing the class that does all the calculations:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plot_data = cwa.analyzer_plots(results)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We'll start by looking at the Output Vs. Time module, which will allow us to plot correlation of our guesses in time. This is useful for finding exactly where the operations we're attacking are. Like in previous tutorials, we'll use bokeh to plot the data we get back.\n",
    "\n",
    "The method we're interested in is `get_plot_data(bnum)`, which returns in a list: `[xrange, correct_key, incorrect_key_data, incorrect_key_data]` for the position `bnum` passed to it. The method returns two sets of incorrect key data because one is for the key guesses below the correct one, and the other is for guesses above the correct one.\n",
    "\n",
    "We'll have a lot of points, so we'll plot as usual, but at the end decimate the output:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def byte_to_color(idx):\n",
    "    return hv.Palette.colormaps['Category20'](idx/16.0)\n",
    "\n",
    "import holoviews as hv\n",
    "from holoviews.operation.datashader import datashade, shade, dynspread, rasterize\n",
    "from holoviews.operation import decimate\n",
    "import pandas as pd, numpy as np\n",
    "\n",
    "a = []\n",
    "b = []\n",
    "hv.extension('bokeh')\n",
    "for i in range(0, 16):\n",
    "    data = plot_data.output_vs_time(i)\n",
    "    a.append(np.array(data[1]))\n",
    "    b.append(np.array(data[2]))\n",
    "    b.append(np.array(data[3]))\n",
    "    \n",
    "pda = pd.DataFrame(a).transpose().rename(str, axis='columns')\n",
    "pdb = pd.DataFrame(b).transpose().rename(str, axis='columns')\n",
    "curve = hv.Curve(pdb['0'], \"Sample\").options(color='black')\n",
    "for i in range(1, 16):\n",
    "    curve *= hv.Curve(pdb[str(i)]).options(color='black')\n",
    "    \n",
    "for i in range(0, 16):\n",
    "    curve *= hv.Curve(pda[str(i)]).options(color=byte_to_color(i))\n",
    "decimate(curve.opts(width=900, height=600))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see some distinctive spikes in your plot. The largest of these is where the sbox lookup is actually happening (the smaller ones are typically other AES operations that move the sbox data around). We are normally talking absolute values, so you'll see negatives in there.\n",
    "\n",
    "This information can be useful in many ways. For example, you can probably see the first 16 spikes that make up the sbox lookup are a small portion of the total trace length. If we ever needed to rerun the attack, we could capture a much smaller number of samples and speed up analysis significantly!"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### PGE vs. Traces\n",
    "\n",
    "The next data we'll look at is a plot of partial guessing entropy (PGE) vs. the number of traces. As mentioned before, PGE is just how many spots away from the top the actual subkey is in our table of guesses. For example, if there are 7 subkey guesses that have a higher correlation than the actual subkey, the subkey has a PGE of 7.\n",
    "\n",
    "This plot is useful for seeing how many traces were needed to actually break the AES implementation. Keep in mind, however, that the resolution of the plot is determined by the reporting interval (also note that `attack_results.find_maximums()` must be called in the callback function). In our case, we have a reporting interval of 10, so we'll have a resolution of 10 traces.\n",
    "\n",
    "This method is similar to the previous plot in that it takes `bnum` as an argument and returns a list of `[xrange, PGE]`. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "ret = plot_data.pge_vs_trace(0)\n",
    "curve = hv.Curve((ret[0],ret[1]), \"Traces Used in Calculation\", \"Partial Guessing Entrop of Byte\")\n",
    "for bnum in range(1, 16):\n",
    "    ret = plot_data.pge_vs_trace(bnum)\n",
    "    curve *= hv.Curve((ret[0],ret[1])).opts(color=byte_to_color(bnum))\n",
    "curve.opts(width=900, height=600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "You should see a number of lines that start off with high values, then rapidly drop off. You may notice that we broke the AES implementation without needing to use all of our traces. \n",
    "\n",
    "Even though we may have broken the AES implementation in fewer traces, we may not want to reduce how many traces we capture. Remember that, while we know the key here, for a real attack we won't and therefore must use the correlation to determine when we've broken a key. Our next plot will help us to determine how feesible capturing fewer traces is."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Correlation vs. Traces"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The last plot we'll take a look at is correlation vs the number of traces. Like with PGE vs. Traces, this plot's resolution is determined by the reporting interval (10 in our case). This method returns a list of `[xrange, [data_for_kguess]]`, so we'll need to plot each guess for each subkey. Like before, we'll do the plot for the correct subkey in a changing color and the rest in black.\n",
    "\n",
    "As you will see, all the subkey guesses start of with large correlations, but all of them except for the correct guess quickly drop off. If you didn't know the key, at what point would you be sure that the guess with the highest correlation was actually the correct subkey?\n",
    "\n",
    "Let's continue and plot the correlations for the right guess and the next best one:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "a = []\n",
    "b = []\n",
    "for bnum in range(0, 16):\n",
    "    data = plot_data.corr_vs_trace(bnum)\n",
    "    best = [0] * len(data[1][0])\n",
    "    for i in range(256):\n",
    "        if i == key[bnum]:\n",
    "            a.append(np.array(data[1][i]))\n",
    "        else:\n",
    "            if max(best) < max(data[1][i]): best = data[1][i]\n",
    "    b.append(np.array(best))\n",
    "\n",
    "pda = pd.DataFrame(a).transpose().rename(str, axis='columns')\n",
    "pdb = pd.DataFrame(b).transpose().rename(str, axis='columns')\n",
    "curve = hv.Curve(pdb['0'].tolist(), \"Iteration Number\", \"Max Correlation\").options(color='black')\n",
    "for i in range(1,len(pdb.columns)):\n",
    "    curve *= hv.Curve(pdb[str(i)]).options(color='black')\n",
    "    \n",
    "for i in range(len(pda.columns)):\n",
    "    curve *= hv.Curve(pda[str(i)]).options(color=byte_to_color(i))\n",
    "            \n",
    "curve.opts(width=900, height=600)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Conclusions & Next Steps\n",
    "\n",
    "As you've seen, Analyzer makes launching a CPA attack much easier than our manual way. It also has the advantage of capturing some interesting data for us, and reporting the attack success every so often.\n",
    "\n",
    "Congratulations, you've reached the end of the main part of SCA101! If you've got a ChipWhisperer-Lite or ChipWhisperer 1200 (Pro), there's a bonus lab that will showcase using a more realisitc trigger. This is also discussed in the slides and training videos."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "---\n",
    "<small>NO-FUN DISCLAIMER: This material is Copyright (C) NewAE Technology Inc., 2015-2020. ChipWhisperer is a trademark of NewAE Technology Inc., claimed in all jurisdictions, and registered in at least the United States of America, European Union, and Peoples Republic of China.\n",
    "\n",
    "Tutorials derived from our open-source work must be released under the associated open-source license, and notice of the source must be *clearly displayed*. Only original copyright holders may license or authorize other distribution - while NewAE Technology Inc. holds the copyright for many tutorials, the github repository includes community contributions which we cannot license under special terms and **must** be maintained as an open-source release. Please contact us for special permissions (where possible).\n",
    "\n",
    "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.</small>"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
